#include "kernel.h"
#include "DescriptorTable.h"
#include "Platform.h"

//#define SINGULARITY_DISTRO_INI_PATH     "singularity/singboot.ini"
#define LOG_RECORD_SIZE                 0x20000
#define LOG_TEXT_SIZE                   0x20000
#define KERNEL_STACK_SIZE               0xC0000

#pragma pack(1)
//#include "halclasswin.h"
#pragma pack()

C_ASSERT(FIELD_OFFSET(GDT, rc) == RM_CODE_SELECTOR);
C_ASSERT(FIELD_OFFSET(GDT, rd) == RM_DATA_SELECTOR);
C_ASSERT(FIELD_OFFSET(GDT, pc) == PM_CODE_SELECTOR);
C_ASSERT(FIELD_OFFSET(GDT, pd) == PM_DATA_SELECTOR);
C_ASSERT(FIELD_OFFSET(GDT, lc) == LM_CODE_SELECTOR);
C_ASSERT(FIELD_OFFSET(GDT, ld) == LM_DATA_SELECTOR);
C_ASSERT(FIELD_OFFSET(GDT, pp) == PROCESSOR_SELECTOR);
C_ASSERT(FIELD_OFFSET(GDT, tss) == TSS_SELECTOR);

PPLATFORM KernelPlatform;
PCPU BlCpuArray;

typedef struct _BL_DISTRO_FILE 
{
    LIST_ENTRY Entry;
    UINT32 Size;
    CHAR Path[1024];
    PVOID Data;
} BL_DISTRO_FILE, *PBL_DISTRO_FILE;

typedef struct _BL_DISTRO 
{
    UINT32 NumberOfFiles;
    UINT32 TotalSize;
    LIST_ENTRY FileList;
    PVOID Data;
} BL_DISTRO, PBL_DISTRO;

BL_DISTRO BlDistro;
PBL_DISTRO_FILE BlKernelFile;

PVOID BlKernelBase;
ULONG_PTR BlKernelSize;

UINT32 (*BlKernelEntryPoint)(PPLATFORM Platform, PCPU Cpu);

PBL_SMAP KernelSmap;
PWCHAR BlCommandLine;

//
// OHCI 1394 buffer for Kernel KD.
//
// AIFIX: Currently, this buffer needs to be allocated in low memory, because
// Kernel KD does not connect if it is allocated in high memory.
//

__declspec(align(PAGE_SIZE)) UINT8 KernelOhci1394Buffer[3 * PAGE_SIZE];

//
// AIFIX: Fix task page definition.
//

typedef struct _BL_PROCESSOR 
{
    UINT32 Index;
    CPU *Cpu;
    PVOID KernelStack;
    ULONG_PTR KernelStackSize;
    PVOID ContextPage;
    PVOID BasePage;
    TSS *TaskPage;
} BL_PROCESSOR, *PBL_PROCESSOR;

PBL_PROCESSOR BlProcessor;
UINT32 BlProcessorCount;

VOID KernelInitializeProcessor(UINT32 Index)
{
    PCPU Processor;

    //
    // Initialize native processor structure.
    //

    Processor = BlProcessor[Index].Cpu;
    Processor->Size = sizeof(CPU);
    Processor->Id = Index;
#if 0
    Processor->ApicId  = Index;
#endif
    Processor->KernelStackLimit = (ULONG_PTR) BlProcessor[Index].KernelStack;
    //    Processor->KernelStackBegin = Processor->KernelStackLimit + KERNEL_STACK_SIZE;
    Processor->KernelStackBegin = Processor->KernelStackLimit + BlProcessor[Index].KernelStackSize;
    Processor->CpuRecordPage = (ULONG_PTR) BlProcessor[Index].BasePage;

    //
    // Initialize task structure.
    //

    BlProcessor[Index].TaskPage->io_bitmap_offset = sizeof(TSS);

    //
    // Initialize segments.
    //

    BlRtlZeroMemory(&Processor->segments, sizeof(Processor->segments));

    Processor->segments.gdtPtr.Base = (ULONG_PTR) &Processor->segments.gdt;
    Processor->segments.gdtPtr.Limit = sizeof(Processor->segments.gdt) - 1;

    BlRtlCopyMemory(&Processor->segments.gdt.pc,
                    (PVOID) (BlMmInitialGdtr.Base + PM_CODE_SELECTOR),
                    sizeof(CODE_SEGMENT));

    BlRtlCopyMemory(&Processor->segments.gdt.pd,
                    (PVOID) (BlMmInitialGdtr.Base + PM_DATA_SELECTOR),
                    sizeof(CODE_SEGMENT));

    BlRtlCopyMemory(&Processor->segments.gdt.lc,
                    (PVOID) (BlMmInitialGdtr.Base + LM_CODE_SELECTOR),
                    sizeof(CODE_SEGMENT));

    BlRtlCopyMemory(&Processor->segments.gdt.ld,
                    (PVOID) (BlMmInitialGdtr.Base + LM_DATA_SELECTOR),
                    sizeof(CODE_SEGMENT));

    BlMmInitializeDataSegment((PDATA_SEGMENT) &Processor->segments.gdt.pp,
                              (UINT32) (ULONG_PTR) BlProcessor[Index].ContextPage,
                              PAGE_SIZE - 1);

    BlMmInitializeSystemSegment((PSYSTEM_SEGMENT) &Processor->segments.gdt.tss,
                                SSDT_AVAILABLE_TSS,
                                (ULONG_PTR) BlProcessor[Index].TaskPage,
                                sizeof(TSS) - 1);

    return;
}

UINT32 KernelProcessorToStart;

UINT32 KernelCallKernel(UINT32 Index)
{
    UINT32 ExitCode;

#if KERNEL_VERBOSE

    BlRtlPrintf("\fProcessor[%d]: Starting Neuron ...\n", Index);

    BlRtlPrintf("Cpu  = %p\n", BlProcessor[Index].Cpu);

    BlRtlPrintf("GDT  =[%p...%p]\n", BlProcessor[Index].Cpu->segments.gdtPtr.addr, BlProcessor[Index].Cpu->segments.gdtPtr.addr + BlProcessor[Index].Cpu->segments.gdtPtr.limit);

    BlRtlPrintf("Stack=[%p...%p]\n", BlProcessor[Index].Cpu->KernelStackLimit, BlProcessor[Index].Cpu->KernelStackBegin);

#endif

    MmSetGdtr((PGDTR) &BlProcessor[Index].Cpu->segments.gdtPtr.Limit);
    MmSetGs(PROCESSOR_SELECTOR);

    __writecr3(BlMmBootCr3);

    BlRtlPrintf("Neuron kernel=[%p ... %p]\n", BlKernelBase, (ULONG_PTR) BlKernelBase + BlKernelSize - 1);
    BlRtlPrintf("Neuron entry= %p\n", BlKernelEntryPoint);

    ExitCode = BlKernelEntryPoint(KernelPlatform, BlProcessor[Index].Cpu);

    MmSetGdtr(&BlMmInitialGdtr);

    BlVideoInitialize();

    return ExitCode;
}

VOID KernelEnterKernel(VOID)
{
    UINT32 ExitCode;
    PVOID LocalVariable;
    UINT32 Index;

    Index = KernelProcessorToStart;

    BLASSERT(((ULONG_PTR) &LocalVariable) >= BlProcessor[Index].Cpu->KernelStackLimit);

    BLASSERT(((ULONG_PTR) &LocalVariable) < BlProcessor[Index].Cpu->KernelStackBegin);

    //
    // The bootstrap processor runs in a loop calling kernel entry point and performing warm boot
    // when the kernel returns. The application processors do not return from the kernel call and are
    // reinitialized with SIPIs by the kernel after a warm boot.
    //

    if (Index == 0) 
{

        for (;;) {
            BlMmDumpPhysicalRegionList();
            ExitCode = KernelCallKernel(Index);

#if KERNEL_VERBOSE

            BlRtlPrintf("Processor[%02x]: Kernel exited with 0x%08x.\n", Index, ExitCode);

#endif

            KernelPlatform->BootCount += 1;

            BlRtlZeroMemory(BlKernelBase, BlKernelSize);

            BlPeLoadImage(BlKernelBase, BlKernelFile->Data, (PVOID *) &BlKernelEntryPoint);
        }

    }
    else {

        ExitCode = KernelCallKernel(Index);

        BlRtlPrintf("BL: AP returned from kernel call!\n");

        SysHalt();
    }

    return;
}

VOID KernelExit(VOID)
{
    MmSetGdtr(&BlMmInitialGdtr);

    BlVideoInitialize();

    switch (KernelPlatform->KillAction) 
{

        case PLATFORM_EXIT_AND_SHUTDOWN: 
{

            BlRtlPrintf("BL: Kernel requested shutdown.\n");

            BlRtlShutdownSystem();

            break;
        }

        case PLATFORM_EXIT_AND_RESTART: 
{

            BlRtlPrintf("BL: Kernel requested restart.\n");

            BlRtlResetSystem();

            break;
        }

        case PLATFORM_EXIT_AND_HALT: 
{

            BlRtlPrintf("BL: Kernel requested halt.\n");

            SysHalt();

            break;
        }

        case PLATFORM_EXIT_AND_WARMBOOT: 

            BlRtlPrintf("BL: Kernel requested warmboot.\n");

            KernelPlatform->BootCount += 1;

            BlRtlZeroMemory(BlKernelBase, BlKernelSize);

            BlPeLoadImage(BlKernelBase, BlKernelFile->Data, (PVOID *) &BlKernelEntryPoint);

            KernelProcessorToStart = 0;

            MmSwitchStack((PVOID) BlProcessor[0].Cpu->KernelStackBegin, KernelEnterKernel);

            break;

        default: 
{

            BlRtlPrintf("BL: Unrecognized kill action 0x%08x!\n", KernelPlatform->KillAction);

            SysHalt();
        }
    }
}


unsigned int IsApicPresent (void)
{
  int CPUInfo[4];

  __cpuid(CPUInfo, 1);
  return ((CPUInfo[3] >> 9) & 0x1);
}

#define INITIAL_APIC_ID_BITS 0xFF000000 // EBX[31:24] unique APIC ID
// Returns the 8-bit unique Initial APIC ID for the processor this
// code is actually running on.  The default value returned is 0xFF if
// Hyper-Threading Technology is not supported.
// Taken from intel application note Ap-485 "Intel Processor Identification and the CPUID instruction"

unsigned char GetAPIC_ID (void)
{
  int CPUInfo[4];

  __cpuid(CPUInfo, 1);
  return (unsigned char) ((CPUInfo[1] & INITIAL_APIC_ID_BITS) >> 24);
}

VOID KernelSetup(UINT32 NumberOfProcessors, PFAR_POINTER ApEntry16, PFAR_POINTER ApStartupLock)
{
    UINT64 Base;
    UINT32 Index;
    PVOID PhysicalRegionHandle;
    UINT64 Size;
    UINT32 Type;
    UINT64 StackStart;

    //
    // Allocate processor array and set processor count.
    //

    BlProcessor = (PBL_PROCESSOR) BlPoolAllocateBlock(sizeof(BL_PROCESSOR) * NumberOfProcessors);
    BlProcessorCount = NumberOfProcessors;

    //
    // Allocate native platform structure.
    //

    KernelPlatform = (PLATFORM *) BlMmAllocatePhysicalRegion(ROUND_UP_TO_PAGES(sizeof(PLATFORM)), BL_MM_PHYSICAL_REGION_NATIVE_PLATFORM);

    KernelPlatform->Size = sizeof(PLATFORM);

    KernelPlatform->hasApic = (IsApicPresent()) ? 1 : 0;

    //
    // Set offsets for per-cpu and per-thread pointers.
    //

    KernelPlatform->CpuRecordPointerOffset = 0;
    KernelPlatform->ThreadRecordPointerOffset = sizeof(PTR_TYPE);

    //
    // Set boot time.
    //

    if (BlStartTime.Year != 0) 
{
        KernelPlatform->BootYear = 2000 + BlStartTime.Year;
        KernelPlatform->BootMonth = BlStartTime.Month;
        KernelPlatform->BootDay = BlStartTime.Day;
        KernelPlatform->BootHour = BlStartTime.Hour;
        KernelPlatform->BootMinute = BlStartTime.Minute;
        KernelPlatform->BootSecond = BlStartTime.Second;
    }

    //
    // Set processor count.
    //

    KernelPlatform->CpuRealCount = NumberOfProcessors;
    KernelPlatform->CpuMaxCount = NumberOfProcessors;

    //
    // Set MPS floating pointer structure address.
    //

    KernelPlatform->MpFloat32 = (UINT32) (ULONG_PTR) BlMpsFps;

    //
    // Set kernel range.
    //

    KernelPlatform->KernelDllBase = (ULONG_PTR) BlKernelBase;
    KernelPlatform->KernelDllFirstPage = (ULONG_PTR) BlKernelBase;
    KernelPlatform->KernelDllSize = BlKernelSize;

    //
    // Set command line.
    //

    KernelPlatform->CommandLine32 = (ULONG_PTR) BlCommandLine;
    KernelPlatform->CommandLineCount = BlRtlStringLengthW(BlCommandLine);

    //
    // Set PNP node list address.
    //

    KernelPlatform->PnpNodesAddr32 = (ULONG_PTR) BlPnpSystemDeviceNodeList;
    KernelPlatform->PnpNodesSize32 = BlPnpSystemDeviceNodeListSize;

    //
    // Set ISA information.
    //

    KernelPlatform->IsaCsns = BlPnpIsaConfiguration.NumberOfCardSelectNumbers;
    KernelPlatform->IsaReadPort = BlPnpIsaConfiguration.DataReadPort;

    //
    // Set PCI BIOS information.
    //

    KernelPlatform->PciBiosAX = BlPciInstallationCheck.Eax;
    KernelPlatform->PciBiosBX = BlPciInstallationCheck.Ebx;
    KernelPlatform->PciBiosCX = BlPciInstallationCheck.Ecx;
    KernelPlatform->PciBiosEDX = BlPciInstallationCheck.Edx;

    //
    // Set VESA information.
    //

    KernelPlatform->VesaBuffer = BlVesaVideoBuffer;

    //
    // Set ACPI information.
    //

    KernelPlatform->AcpiRoot32 = (ULONG_PTR) BlAcpiRsdpAddress;

    //
    // Set file image table.
    //

  //  KernelPlatform->FileImageTableBase32 = (ULONG_PTR) KernelFileImageTable;
  //  KernelPlatform->FileImageTableEntries = KernelFileImageTableSize;

    //
    // Allocate log record and text buffers.
    //

    KernelPlatform->LogRecordBuffer = (ULONG_PTR) BlMmAllocatePhysicalRegion(LOG_RECORD_SIZE, BL_MM_PHYSICAL_REGION_LOG_RECORD);
    KernelPlatform->LogRecordSize = LOG_RECORD_SIZE;

    KernelPlatform->LogTextBuffer = (ULONG_PTR) BlMmAllocatePhysicalRegion(LOG_TEXT_SIZE, BL_MM_PHYSICAL_REGION_LOG_TEXT);
    KernelPlatform->LogTextSize = LOG_TEXT_SIZE;

    //
    // Set debugger settings.
    //

    if (BlKdComPort != 0) 
{
        KernelPlatform->DebuggerType = PLATFORM_DEBUGGER_SERIAL;
        KernelPlatform->DebugBasePort = BlComBasePort[BlKdComPort];

    }
    else if (BlPciOhci1394BaseAddress != 0) 
{
        BlRtlPrintf("Got 1394 debugger base address!\n");
        KernelPlatform->DebuggerType = PLATFORM_DEBUGGER_1394;
        KernelPlatform->Ohci1394Base = BlPciOhci1394BaseAddress;
        KernelPlatform->Ohci1394BufferAddr32 = (ULONG_PTR) KernelOhci1394Buffer;
        KernelPlatform->Ohci1394BufferSize32 = sizeof(KernelOhci1394Buffer);

    }
    else 
{
        BlRtlPrintf("Debugger OFF\n");
        KernelPlatform->DebuggerType = PLATFORM_DEBUGGER_NONE;
    }

    KernelPlatform->TwiddleSpinBase = 0xb8000;

    //
    // Set exit routine.
    //

    KernelPlatform->Kill32 = (ULONG_PTR) KernelExit;

    //
    // Set entry routine and startup lock address for application processors.
    //

    KernelPlatform->MpEnter32 = (ULONG_PTR) BlRtlConvertFarPointerToLinearPointer(ApEntry16);
    KernelPlatform->MpStartupLock32 = (ULONG_PTR) BlRtlConvertFarPointerToLinearPointer(ApStartupLock);

    //
    // Allocate native processor structures.
    //

    BlCpuArray = (CPU *) BlMmAllocatePhysicalRegion(ROUND_UP_TO_PAGES(sizeof(CPU) * NumberOfProcessors), BL_MM_PHYSICAL_REGION_NATIVE_PROCESSOR);

    KernelPlatform->Cpus = (ULONG_PTR) BlCpuArray;

    //
    // Allocate per-processor resources upfront.
    //

    for (Index = 0; Index < NumberOfProcessors; Index += 1) 
{
        BlProcessor[Index].Index = Index;
        BlProcessor[Index].Cpu = &BlCpuArray[Index];
        BlProcessor[Index].ContextPage = (PVOID) BlMmAllocatePhysicalRegion(2 * PAGE_SIZE, BL_MM_PHYSICAL_REGION_CONTEXT);
        BlProcessor[Index].BasePage = (PVOID) ((ULONG_PTR) BlProcessor[Index].ContextPage + PAGE_SIZE);
        BlProcessor[Index].TaskPage = (TSS *) BlMmAllocatePhysicalRegion(PAGE_SIZE, BL_MM_PHYSICAL_REGION_TASK);
    }

    //
    // Allocate kernel stack for the bootstrap processor.
    //

    StackStart = BlMmAllocatePhysicalRegion(KERNEL_STACK_SIZE,
                        BL_MM_PHYSICAL_REGION_KERNEL_STACK);
    if (0 == StackStart) {
    BlRtlPrintf("Failed to allocate kernel stack.\n", StackStart);
    SysHalt();
    }

    BlRtlPrintf("Allocated kernel stack at 0x%016I64x\n", StackStart);

    BlProcessor[0].KernelStack = (PVOID) (StackStart);
    BlProcessor[0].Cpu->DomainBsp = TRUE;

    KernelPlatform->OutgoingMessage   = 0;
    KernelPlatform->OutgoingCount     = 0;
    KernelPlatform->IncomingFree      = 0;
    KernelPlatform->IncomingFreeCount = 0;
    KernelPlatform->IncomingMessage   = 0;
    KernelPlatform->IncomingCount     = 0;
    KernelPlatform->OutgoingFree      = 0;
    KernelPlatform->OutgoingFreeCount = 0;
    KernelPlatform->MaxBufferLength   = 0;

    BlProcessor[0].KernelStackSize = KERNEL_STACK_SIZE;

    //
    // Initialize the first processor structure.
    //

    KernelInitializeProcessor(0);

    //
    // Allocate memory map for Kernel.
    //

    KernelSmap = (PBL_SMAP) BlMmAllocatePhysicalRegion(sizeof(BL_SMAP), BL_MM_PHYSICAL_REGION_KERNEL_SMAP);

    //
    // Claim all remaining physical memory for Kernel.
    //

    while (BlMmFindFreePhysicalRegion(&Base, &Size) != FALSE) {

        BlMmAllocateSpecificPhysicalRegion(Base, Size, BL_MM_PHYSICAL_REGION_KERNEL);
    }

    //
    // Generate memory map for Kernel.
    //

    BlRtlZeroMemory(KernelSmap, sizeof(BL_SMAP));

    KernelPlatform->PhysicalBase = (ULONG_PTR) -1;

    PhysicalRegionHandle = NULL;

    while (BlMmGetNextPhysicalRegion(&PhysicalRegionHandle,
                                     &Base,
                                     &Size,
                                     &Type) != FALSE) {

        if ((Type == BL_MM_PHYSICAL_REGION_KERNEL) ||
            (Type == BL_MM_PHYSICAL_REGION_KERNEL_IMAGE) ||
            (Type == BL_MM_PHYSICAL_REGION_NATIVE_PLATFORM) ||
            (Type == BL_MM_PHYSICAL_REGION_NATIVE_PROCESSOR) ||
            (Type == BL_MM_PHYSICAL_REGION_KERNEL_STACK)
            ) {

            if (Type == BL_MM_PHYSICAL_REGION_KERNEL) {
                KernelSmap->Entry[KernelSmap->EntryCount].Base = Base;
                KernelSmap->Entry[KernelSmap->EntryCount].Size = Size;
                KernelSmap->Entry[KernelSmap->EntryCount].Type = BL_SMAP_AVAILABLE;
            }
            else if (Type == BL_MM_PHYSICAL_REGION_KERNEL_IMAGE ||
                      (Type == BL_MM_PHYSICAL_REGION_NATIVE_PLATFORM) ||
                      (Type == BL_MM_PHYSICAL_REGION_NATIVE_PROCESSOR)) {
                KernelSmap->Entry[KernelSmap->EntryCount].Base = Base;
                KernelSmap->Entry[KernelSmap->EntryCount].Size = Size;
                KernelSmap->Entry[KernelSmap->EntryCount].Type = BL_SMAP_KERNEL_NONGC;
            }
            else if (Type == BL_MM_PHYSICAL_REGION_KERNEL_STACK) {
                KernelSmap->Entry[KernelSmap->EntryCount].Base = Base;
                KernelSmap->Entry[KernelSmap->EntryCount].Size = Size;
                KernelSmap->Entry[KernelSmap->EntryCount].Type = BL_SMAP_KERNEL_STACK;
            }
            else {
                BlRtlPrintf("UNKNOWN TYPE MEMORY??? %d\n", Type);
            }
            if (Base < KernelPlatform->PhysicalBase) {
                KernelPlatform->PhysicalBase = (ULONG_PTR) Base;
            }
            KernelSmap->EntryCount += 1;
        }
        else {
#if MM_VERBOSE
            BlRtlPrintf("Building SMAP marking type %s nongc\n", BlMmPhysicalRegionTypeString(Type));
#endif
            KernelSmap->Entry[KernelSmap->EntryCount].Base = Base;
            KernelSmap->Entry[KernelSmap->EntryCount].Size = Size;
            KernelSmap->Entry[KernelSmap->EntryCount].Type = BL_SMAP_KERNEL_NONGC;
            KernelSmap->EntryCount += 1;
        }
    }

    KernelPlatform->Smap32 = (ULONG_PTR) (PVOID) KernelSmap->Entry;
    KernelPlatform->SmapCount = KernelSmap->EntryCount;

#if MM_VERBOSE

    BlMmDumpPhysicalRegionList();

#endif

    //
    // Start processor 0.
    //

    KernelProcessorToStart = 0;

    MmSwitchStack((PVOID) BlProcessor[0].Cpu->KernelStackBegin, KernelEnterKernel);

    return;
}

VOID KernelApEntry(VOID)

//++
//
//  Routine Description:
//
//    This function implements the entry point for application processors.
//
//--

{
    UINT32 Index;

    //    BlRtlPrintf("In Ap Entry\n");

    //    BlRtlPrintf("Non AP processor booting\n");
    Index = KernelPlatform->MpBootInfo.TargetCpu;

    if (Index == 0) {

        // BlRtlPrintf("BL: BSP entered AP code!\n");
        SysHalt();
    }

#if KERNEL_VERBOSE

    BlRtlPrintf("BL: Initializing processor %u of %u. [Max=%u]\n",
                Index + 1,
                KernelPlatform->CpuRealCount,
                KernelPlatform->CpuMaxCount);

#endif

    BlProcessor[Index].KernelStack = (PVOID) KernelPlatform->MpBootInfo.KernelStackLimit;
    BlProcessor[Index].Cpu->DomainBsp = FALSE;
    BlProcessor[Index].KernelStackSize = KernelPlatform->MpBootInfo.KernelStackBegin - KernelPlatform->MpBootInfo.KernelStackLimit;

    KernelInitializeProcessor(Index);

    KernelProcessorToStart = Index;

    MmSwitchStack((PVOID) BlProcessor[Index].Cpu->KernelStackBegin, KernelEnterKernel);

    return;
}
